/*
 * Project CelestialBeing: data_arch.hpp
 * Created: 2023/12/16
 * This program uses MulanPSL2. See LICENSE for more.
 */

// DataTypeID is a library for generating compile data type identity information.
// This is not reflection. It's much simpler.
// Supports: Compile time generation & Runtime query.
// (We don't support runtime generation cause it is not needed.)
// We use it to generate data architecture information for the archive system type checking.

#ifndef CELESTIALBEING_TYPE_ID_HPP
#define CELESTIALBEING_TYPE_ID_HPP
#include "cb/common/celestial_being_base.hpp"
#include "charseq.hpp"
#include "consteval.hpp"

CB_NAMESPACE_BEGIN

struct DataTypeID_Wrapper {
    enum DataTypeID : uint64_t {
        kInvalid = UINT64_MAX
    };
};
// DataTypeID is a hash value for identify a data type.
// It is used for typechecking such as in archives.
// Use ConstMakeDataTypeID to make a DataTypeID.
// Use ConstGetDataTypeMeta<T>().id or TGetDataTypeMeta<T>::value.id to get a DataTypeID for comparison.
typedef DataTypeID_Wrapper::DataTypeID DataTypeID;

template<size_t TypeNameLength>
struct DataTypeMeta {
    // The name identifies the type
    char name[TypeNameLength] {};
    // Data ID identifies the data structure of the type and revision.
    // Name ID is the hash value of name.
    // ID is the hash value of Data ID and Name ID.
    DataTypeID name_id {};
    DataTypeID data_id {};
    DataTypeID id {};
    // Revision specifies the "version" of the data structure.
    // Updating revision number forces the update of data_id
    uint64_t   revision {};
};

// The template to get the datatype id for any type.
template<typename T> struct TGetDataTypeMeta ;

namespace details {
    template<typename...Types>
    constexpr uint64_t THashTypeSequence_v = CB_NAMESPACE::ConstHashNumberSeries(TGetDataTypeMeta<Types>::value.id...);
    template<>
    constexpr uint64_t THashTypeSequence_v<> = 0;

    template<size_t Len>
    consteval DataTypeMeta<Len> ConstCreateDataTypeMeta (const char (&Name)[Len], DataTypeID data_id_no_revision, uint64_t Revision) {
        DataTypeMeta<Len> result;
        const char * NameIter = Name;
        std::copy(NameIter, NameIter + Len, result.name);
        result.data_id = static_cast<DataTypeID>(ConstHashNumberSeries(static_cast<uint64_t>(data_id_no_revision), 0x667788abcull, Revision ^ C::kHashXorMapValueSingleUInt64));
        result.name_id = static_cast<DataTypeID>(ConstCStringHash(Name));
        result.revision = Revision;
        result.id = static_cast<DataTypeID>(ConstHashNumberSeries(static_cast<uint64_t>(result.data_id), 0x19260817ull, static_cast<uint64_t>(result.name_id)));
        return result;

    }
}

// Create a datatype meta.
// The typename specifies the identity of the type.
// The revision specifies the version of the datastructure of the type.
// SubTypes... are sub datatypes included by this datatype.

// Sample usage, put the followings inside the declaration of class RandomObjectState:
// constexpr static char kTypeName[] = "RandomObjectState";
// constexpr static auto kDataType = TMakeDataTypeMeta<kTypeName, 0, SomeSubType1, SomeSubType2>;

// NOTE: holding datatype pointers are not considered as subtypes.
// For example, CharacterState is not a subtype of TSerializableSharedPtr<CharacterState>.
template<const auto & TypeName, size_t Revision = 0, typename...SubTypes>
struct TMakeDataTypeMeta {
    using ArrType = std::remove_cvref_t<decltype(TypeName)>;
    constexpr static size_t ArrLength = std::extent_v<ArrType>;
    static_assert(ArrLength > 0, "Type name must not be empty. ");
    static_assert(std::is_same_v<std::remove_all_extents_t<ArrType>, std::remove_extent_t<ArrType>>, "Type name must not be a multi-dimensional array.");
    static_assert(std::is_same<std::remove_all_extents_t<ArrType>, char>::value, "Type name must be a char array.");
    constexpr static auto value =
            ::CB_NAMESPACE::details::ConstCreateDataTypeMeta(
            TypeName,
            static_cast<DataTypeID>(::CB_NAMESPACE::details::THashTypeSequence_v<SubTypes...>),
                Revision
            );
};

namespace detail {
    template<typename T, typename = void> struct TGetDataTypeMeta {
        // Must have datatype meta for using this template.
        // If you see this error, you must add a data type meta to the class.
        // Using CB_DATATYPE_DECL(Name) or CB_DATATYPE_DECL_V(Name, Revision) or CB_DATATYPE_DECL_VA(Name, Revision, Args...)
        static_assert("Datatype meta required.");
        using NoDataTypeMeta = void;
    };
    // Register some class to the data type meta system. The basic ones include: int, float, double, char, bool, void, uint64_t, uint32_t, nullptr_t.
    template<> struct TGetDataTypeMeta<int> {
        constexpr static char name[] = "int";
        constexpr static auto value = TMakeDataTypeMeta<name>::value;
    };
    template<> struct TGetDataTypeMeta<float> {
        constexpr static char name[] = "float";
        constexpr static auto value = TMakeDataTypeMeta<name>::value;
    };
    template<> struct TGetDataTypeMeta<double> {
        constexpr static char name[] = "double";
        constexpr static auto value = TMakeDataTypeMeta<name>::value;
    };
    template<> struct TGetDataTypeMeta<char> {
        constexpr static char name[] = "char";
        constexpr static auto value = TMakeDataTypeMeta<name>::value;
    };
    template<> struct TGetDataTypeMeta<bool> {
        constexpr static char name[] = "bool";
        constexpr static auto value = TMakeDataTypeMeta<name>::value;
    };
    template<> struct TGetDataTypeMeta<uint32_t> {
        constexpr static char name[] = "uint32_t";
        constexpr static auto value = TMakeDataTypeMeta<name>::value;
    };
    template<> struct TGetDataTypeMeta<uint64_t> {
        constexpr static char name[] = "uint64_t";
        constexpr static auto value = TMakeDataTypeMeta<name>::value;
    };
    template<> struct TGetDataTypeMeta<int64_t> {
        constexpr static char name[] = "int64_t";
        constexpr static auto value = TMakeDataTypeMeta<name>::value;
    };
    // Pointer type id is the type id of the element type with the pointer marker.
    template<typename T> struct TGetDataTypeMeta<T *> {
        constexpr static char format[] = "({})*";
        constexpr static auto value = TMakeDataTypeMeta<FORMAT_1P(format, TGetDataTypeMeta<T>::value.name), 0>::value;
    };
    // Array type id is the type id of the element type append array size id.
    template<typename T, size_t N> struct TGetDataTypeMeta<T[N]> {
        constexpr static char format[] = "({})[{}]";
        // Setting this value is due to a flaw in C++ design.
        // The template TCharSeq is designed to accept both C char arrays and unsigned integers.
        // However, C arrays automatically decays to pointers unless we use references to capture them as template parameters.
        // In the meantime, literal integer constants can not be referenced. So we must explicitly define a referencable value here.
        constexpr static size_t N_value = N;
        constexpr static auto value = TMakeDataTypeMeta<FORMAT_2P(format, TGetDataTypeMeta<T>::value.name, N_value), 0>::value;
    };
    // STL containers.
    // std::vector
    template<typename T, typename A> struct TGetDataTypeMeta<std::vector<T, A>> {
        constexpr static char format[] = "std::vector<{}>";
        constexpr static auto value = TMakeDataTypeMeta<FORMAT_1P(format, TGetDataTypeMeta<T>::value.name)>::value;
    };
    // std::map
    template<typename K, typename V, typename C, typename A> struct TGetDataTypeMeta<std::map<K, V, C, A>> {
        constexpr static char format[] = "std::map<{}, {}>";
        constexpr static auto value = TMakeDataTypeMeta<FORMAT_2P(format, TGetDataTypeMeta<K>::value.name, TGetDataTypeMeta<V>::value.name)>::value;
    };
    // std::basic_string
    template<typename C, typename T, typename A> struct TGetDataTypeMeta<std::basic_string<C, T, A>> {
        constexpr static char format[] = "std::basic_string<{}>";
        constexpr static DataTypeMeta value = TMakeDataTypeMeta<FORMAT_1P(format, TGetDataTypeMeta<C>::value.name)>::value;
    };
    // std::array
    template<typename T, size_t N> struct TGetDataTypeMeta<std::array<T, N>> {
        constexpr static char format[] = "std::array<{}, {}>";
        constexpr static size_t N_value = N;
        constexpr static DataTypeMeta value = TMakeDataTypeMeta<FORMAT_2P(format, TGetDataTypeMeta<T>::value.name, N_value)>::value;
    };

    // Helper to determine if a type is an instance of DataTypeMeta
    template<typename T>
    struct TIsInstanceOfDataTypeMeta : std::false_type {};
    template<size_t Len>
    struct TIsInstanceOfDataTypeMeta<DataTypeMeta<Len>> : std::true_type {};

    // Use T::kDataTypeMeta if present.
    template<typename T> struct TGetDataTypeMeta<T, std::enable_if_t<TIsInstanceOfDataTypeMeta<std::remove_cvref_t<decltype(T::kDataTypeMeta)>>::value>> {
        constexpr static auto value = T::kDataTypeMeta;
    };
    // Popup an error if T::kDataTypeMeta is not of type DataTypeMeta.
    template<typename T> struct TGetDataTypeMeta<T, std::enable_if_t<!TIsInstanceOfDataTypeMeta<std::remove_cvref_t<decltype(T::kDataTypeMeta)>>::value>> {
        // Delay the expression evaluation to template evaluation to supress compiler error.
        static_assert(TDelayedFalseType<T>::value && "T::kDataTypeMeta must be of type DataTypeMeta. ");
    };
}
// Check if a class has a data type meta.
template<typename T, typename=void> struct THasDataTypeMeta : std::true_type {};
template<typename T> struct THasDataTypeMeta<T, typename detail::TGetDataTypeMeta<std::remove_cvref_t<T>>::NoDataTypeMeta> : std::false_type {};

// Get the data type meta of some class. Executed at compile time.
// const/volatile/reference values of the same class evaluate to the same meta.
template<typename T> consteval auto ConstGetDataTypeMeta() {
    return detail::TGetDataTypeMeta<std::remove_cvref_t<T>>::value;
}
// Get the data type meta of some class.
// const/volatile/reference values of the same class evaluate to one number.
template<typename T> struct TGetDataTypeMeta {
    static_assert(THasDataTypeMeta<T>::value, "T must have a data type meta. ");
    constexpr static DataTypeMeta value = detail::TGetDataTypeMeta<std::remove_cvref_t<T>>::value;
};
// Functions have no data type meta.
template<typename T, typename...Args> struct TGetDataTypeMeta<T(Args...)> {
    static_assert(TDelayedFalseType<T>::value && "Functions have no data type meta. ");
};

#define CB_DATATYPE_DECL_F(Name, Revision, ...) \
public: constexpr static char kTypeName[] = Name;\
constexpr static auto kDataTypeMeta = ::CB_NAMESPACE::TMakeDataTypeMeta<kTypeName, Revision, __VA_ARGS__>::value;

#define CB_DATATYPE_DECL(Name, Revision) \
public: constexpr static char kTypeName[] = Name;\
constexpr static auto kDataTypeMeta = ::CB_NAMESPACE::TMakeDataTypeMeta<kTypeName, Revision>::value;

#define CB_DATATYPE_DECL_INPLACE_F(Name, Revision, ...) \
constexpr static auto kDataTypeMeta = ::CB_NAMESPACE::TMakeDataTypeMeta<Name, Revision, __VA_ARGS__>::value;

#define CB_DATATYPE_DECL_INPLACE(Name, Revision) \
constexpr static auto kDataTypeMeta = ::CB_NAMESPACE::TMakeDataTypeMeta<Name, Revision>::value;

CB_NAMESPACE_END

#endif //CELESTIALBEING_TYPE_ID_HPP
